package org.duang.http.core;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Map.Entry;

import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.duang.http.common.ClientException;
import org.duang.http.common.RepeatableInputStreamEntity;
import org.duang.http.core.ServiceClient.Request;
import org.duang.http.core.enums.HttpMethod;
import org.duang.kit.ToolsKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.net.HttpHeaders;

public class HttpFactory {

	private static Logger logger = LoggerFactory.getLogger(HttpFactory.class);

	private static PoolingNHttpClientConnectionManager getConnectionManager(ClientConfig config) {
			IOReactorConfig ioConfig = IOReactorConfig.custom().setIoThreadCount(config.getIoReactorThreadCount())
					.setConnectTimeout(config.getConnectionTimeout()).setSoTimeout(config.getSocketTimeout())
					.setSoKeepAlive(config.isSoKeepAlive()).build();
			ConnectingIOReactor ioReactor = null;
			try {
				ioReactor = new DefaultConnectingIOReactor(ioConfig);
			} catch (IOReactorException e) {
				throw new ClientException(e.getMessage(), e);
			}
			if (ToolsKit.isEmpty(ioReactor)) {
				throw new ClientException("ConnectingIOReactor is null");
			}
			PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
			connManager.setDefaultMaxPerRoute(config.getMaxConnectionsPerRoute());
			connManager.setMaxTotal(config.getMaxConnections());
		return connManager;
	}

	private static RequestConfig getRequestConfig(ClientConfig config) {
		RequestConfig requestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.BEST_MATCH).setExpectContinueEnabled(true)
					.setStaleConnectionCheckEnabled(true)
					.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST))
					.setConnectTimeout(config.getConnectionTimeout()).setSocketTimeout(config.getSocketTimeout())
					.setExpectContinueEnabled(config.isExceptContinue()).build();
		return requestConfig;
	}

	public static CloseableHttpAsyncClient createHttpAsyncClient(ClientConfig config) {
		HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClients.custom()
				.setConnectionManager(getConnectionManager(config));
		httpClientBuilder.setDefaultRequestConfig(getRequestConfig(config))
				.setMaxConnPerRoute(config.getMaxConnectionsPerRoute()).setMaxConnTotal(config.getMaxConnections())
				.setUserAgent(config.getUserAgent());
		CloseableHttpAsyncClient httpClient = httpClientBuilder.build();
		logger.debug("createHttpAsyncClient : "+  httpClient.hashCode() + " is success!");
		return httpClient;
	}

	public static HttpRequestBase createHttpRequest(Request request) {
		String url = request.getUrl();
		HttpMethod method = request.getMethod();
		HttpRequestBase httpRequestBase;
		if (method == HttpMethod.GET) {
			httpRequestBase = new HttpGet(url);
		} else if (method == HttpMethod.POST) {
			HttpPost postMethod = new HttpPost(url);
			if (ToolsKit.isNotEmpty(request.getContent())) {
				postMethod.setEntity(new RepeatableInputStreamEntity(request));
			}
			httpRequestBase = postMethod;
		} else if (method == HttpMethod.PUT) {
			httpRequestBase = new HttpPut(url);
		} else if (method == HttpMethod.DELETE) {
			httpRequestBase = new HttpDelete(url);
		} else if (method == HttpMethod.HEAD) {
			httpRequestBase = new HttpHead(url);
		} else if (method == HttpMethod.OPTIONS) {
			httpRequestBase = new HttpOptions(url);
		} else {
			throw new ClientException(String.format("Unsupported HTTP method: %s.", request.getMethod().toString()));
		}
		setRequestHeaders(request, httpRequestBase);
		return httpRequestBase;
	}
	
	private static void setRequestHeaders(Request request, HttpRequestBase httpRequestBase) {
		for(Iterator<Entry<String,String>> it = request.getHeaders().entrySet().iterator(); it.hasNext();) {
			Entry<String,String> entry = it.next();
			String key = entry.getKey();
			if(key.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH) || key.equalsIgnoreCase(HttpHeaders.HOST)){
				continue;
			}
			httpRequestBase.addHeader(key, entry.getValue());
		}
	}
}
